package com.wn.icloud.plugin.util;

import com.google.common.collect.Sets;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.reflections.ReflectionUtils;
import org.reflections.Reflections;
import org.springframework.web.bind.annotation.*;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author kyy
 * @date 2018/09/29
 */
public class ClassScanner {


    public static List<ApiController> scanController(String basePackage) {
        Reflections reflections = new Reflections(basePackage);
        Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(RestController.class);
        List<ApiController> collect1 = typesAnnotatedWith.stream()
                .map(controller -> {
                    Set<Method> getMethods = ReflectionUtils.getMethods(controller,
                            ReflectionUtils.withAnnotation(GetMapping.class)
                    );
                    Set<Method> postMethods = ReflectionUtils.getMethods(controller,
                            ReflectionUtils.withAnnotation(PostMapping.class)
                    );
                    Set<Method> deleteMethods = ReflectionUtils.getMethods(controller,
                            ReflectionUtils.withAnnotation(DeleteMapping.class)
                    );
                    Set<Method> putMethods = ReflectionUtils.getMethods(controller,
                            ReflectionUtils.withAnnotation(PutMapping.class)
                    );

                    Set<Method> otherMethods = ReflectionUtils.getMethods(controller,
                            ReflectionUtils.withAnnotation(RequestMapping.class)
                    );
                    HashSet<Method> sets = Sets.newHashSet();
                    sets.addAll(getMethods);
                    sets.addAll(postMethods);
                    sets.addAll(deleteMethods);
                    sets.addAll(putMethods);
                    sets.addAll(otherMethods);

                    ApiController apiController = new ApiController();
                    List<ApiMethod> collect = sets.stream().map(m -> getMethod(controller, m)).collect(Collectors.toList());
                    String[] split = controller.getName().split("\\.");
                    String controllerName = split[split.length - 1].replace("Controller", "");
                    apiController.setName(controllerName);
                    apiController.setMethods(collect);

                    String moduleName = controller.getPackage().getName().replaceAll(basePackage, "");
                    moduleName = moduleName.replaceAll("controller", "");
                    apiController.setModule(moduleName);

                    return apiController;
                })
                .collect(Collectors.toList());

        return collect1;
    }

    @Data
    public static class ApiController {
        private String name;
        List<ApiMethod> methods;
        private String module;
    }

    @Data
    private static class ApiMethod {
        private String method;
        private String name;
        private String path;
        private String dataOrParams;
    }

    private static final Class[] REQUEST_MAPPING_ANNOTATIONS = {
            RequestMapping.class,
            GetMapping.class,
            PostMapping.class,
            PutMapping.class,
            DeleteMapping.class
    };

    private static ApiMethod getMethod(Class<?> controller, Method method) {

        StringBuffer path = new StringBuffer("");

        String requestMappingAnnotationValue1 = getRequestMappingAnnotationValue(controller.getAnnotation(RequestMapping.class));

        if (StringUtils.isNotBlank(requestMappingAnnotationValue1)) {
            path.append(requestMappingAnnotationValue1);
        }
        Optional<Annotation> first = Arrays.stream(REQUEST_MAPPING_ANNOTATIONS)
                .map(a -> method.getAnnotation(a))
                .filter(Objects::nonNull)
                .findFirst();
        if (first.isPresent()) {
            String requestMappingAnnotationValue = getRequestMappingAnnotationValue(first.get());
            if (StringUtils.isNotBlank(requestMappingAnnotationValue)) {
                path.append(requestMappingAnnotationValue);
            }
        } else {
            return null;
        }

        if (path.length() == 0) {
            return null;
        }
        ApiMethod apiMethod = new ApiMethod();
        Tuple<String, String> httpMethod = getHttpMethod(first.get());
        apiMethod.setMethod(httpMethod.x);
        apiMethod.setDataOrParams(httpMethod.y);
        apiMethod.setPath(path.toString().replaceAll("//", "/"));
        apiMethod.setName(method.getName());
        return apiMethod;
    }


    private static String getRequestMappingAnnotationValue(Annotation annotation) {
        if (annotation == null) {
            return null;
        }
        String[] value = null;
        String name = annotation.annotationType().getName();
        if (name.equals(GetMapping.class.getName())) {
            value = ((GetMapping) annotation).value();
        } else if (name.equals(PutMapping.class.getName())) {
            value = ((PutMapping) annotation).value();
        } else if (name.equals(PostMapping.class.getName())) {
            value = ((PostMapping) annotation).value();
        } else if (name.equals(DeleteMapping.class.getName())) {
            value = ((DeleteMapping) annotation).value();
        } else if (name.equals(RequestMapping.class.getName())) {
            value = ((RequestMapping) annotation).value();
        }
        if (value != null && value.length > 0) {
            return value[0];
        }
        return null;
    }


    public static class Tuple<X, Y> {

        public X x;
        public Y y;

        public Tuple(X x, Y y) {
            this.x = x;
            this.y = y;
        }
    }

    private static Tuple<String, String> getHttpMethod(Annotation annotation) {
        String name = annotation.annotationType().getName();
        if (name.equals(GetMapping.class.getName())) {
            return new Tuple<>("get", "params");
        } else if (name.equals(PutMapping.class.getName())) {
            return new Tuple<>("put", "data");
        } else if (name.equals(PostMapping.class.getName())) {
            return new Tuple<>("post", "data");
        } else if (name.equals(DeleteMapping.class.getName())) {
            return new Tuple<>("delete", "params");
        } else if (name.equals(RequestMapping.class.getName())) {
            return new Tuple<>("get", "params");
        }
        return new Tuple<>("put", "params");

    }

}
